#ifndef CLASS_JS
#define CLASS_JS

#include "Concept.js"
#include "ConceptCollection.js"
#include "Constructor.js"
#include "Field.js"
#include "Method.js"
#include "Patterns.js"

/* class */ Class = function(/* Concept */ concept)
{
	var /* string */ _name;
	var /* Constructor */ _constructor;
	var /* Field[] */ _fields;
	var /* Method[] */ _methods;
	
	var /* bool */ _isStatic = false;
	var /* bool */ _containsStatic = false;
	var /* Constructor */ _staticConstructor;
	var /* Field[] */ _staticFields;
	var /* Method[] */ _staticMethods;
	
	(function()
	{
		var captures = Class.RegExp.exec(concept.Head);
		
		_isStatic = (captures[Class.StaticPosition] != undefined);
		
		_name = captures[Class.NamePosition];
		
		_fields = new Array();
		_staticFields = new Array();
		_methods = new Array();
		_staticMethods = new Array();
		var subConcepts = new ConceptCollection(concept.Body);
		for (var i = 0; i < subConcepts.Length; i++)
		{
			var subConcept = subConcepts.At(i);
			
			if (Constructor.IsMatching(_name, subConcept))
			{
				var constructor = new Constructor(subConcept);
			
				if (constructor.IsStatic)
				{
					if(_staticConstructor != null)
					{
						throw "Only one static constructor can be defined.";
					}
					
					_staticConstructor = constructor;
					_containsStatic = true;
				}
				else
				{
					if (_constructor != null)
					{
						throw "Only one constructor can be defined.";
					}
					
					if (_isStatic)
					{
						throw "A static class cannot have a non-static constructor.";
					}
					
					_constructor = constructor;
				}
			}
			else if (Field.IsMatching(subConcept))
			{
				var field = new Field(subConcept);
				
				if (field.Name == _name)
				{
					throw "A field cannot have the name of its class.";
				}
				
				// TODO: Check that the field and method names (static or not) are not equal.
				
				if (field.IsStatic)
				{
					_staticFields.push(field);
					_containsStatic = true;
				}
				else
				{
					if (_isStatic)
					{
						throw "A static class cannot have a non-static field.";
					}
					
					_fields.push(field);
				}
			}
			else if(Method.IsMatching(subConcept))
			{
				var method = new Method(subConcept);
				
				if (method.Name == _name)
				{
					throw "A method cannot have the name of its class.";
				}
				
				// TODO: Check that the field and method names (static or not) are not equal.
				
				if (method.IsStatic)
				{
					_staticMethods.push(method);
					_containsStatic = true;
				}
				else
				{
					if (_isStatic)
					{
						throw "A static class cannot have a non-static method.";
					}
					
					_methods.push(method);
				}
			}
		}
		
		if (_constructor == null)
		{
			_constructor = new Constructor(new Concept("public " + _name + "()", ""));
		}
	})();
	
	this.__defineGetter__("Name", function() { return _name; });
	this.__defineSetter__("Name", function() { throw "MethodAccessibilityException"; });

	this.__defineGetter__("Constructor", function() { return _constructor; });
	this.__defineSetter__("Constructor", function() { throw "MethodAccessibilityException"; });
	
	this.__defineGetter__("Fields", function() { return _fields; });
	this.__defineSetter__("Fields", function() { throw "MethodAccessibilityException"; });
	
	this.__defineGetter__("Methods", function() { return _methods; });
	this.__defineSetter__("Methods", function() { throw "MethodAccessibilityException"; });
	
	this.ToJs = function()
	{
		var output = "";
		
		if (_containsStatic)
		{
			output += "var " + _name + "={};";
			
			if (_staticConstructor != null)
			{
				output += "var __" + _name + "StaticConstructor;";
			}
			
			output += "(function(){";
			
			for (var i = 0; i < _staticFields.length; i++)
			{
				output += _staticFields[i].ToJs();
			}
			
			for (var i = 0; i < _staticMethods.length; i++)
			{
				output += _staticMethods[i].ToPrivateStaticJs();
			}
			
			if (_staticConstructor != null)
			{
				output += "__" + _name + "StaticConstructor=function(){" + _staticConstructor.Body + "};";
			}
		}
		
		if (!_isStatic)
		{
			if (!_containsStatic)
			{
				output += "var ";
			}
			
			output += _name + "=function(";
			if (_constructor != null)
			{
				output += _constructor.Signature;
			}
			output += "){";

			for (var i = 0; i < _fields.length; i++)
			{
				output += _fields[i].ToJs();
			}

			for (var i = 0; i < _methods.length; i++)
			{
				output += _methods[i].ToNonStaticJs();
			}
		
			if (_constructor != null)
			{
				output += _constructor.Body;
			}
		
			output += "};";
		}
		
		if (_containsStatic)
		{
			for (var i = 0; i < _staticMethods.length; i++)
			{
				output += _staticMethods[i].ToPublicStaticJs(_name);
			}
		}
		
		if (_isStatic || _containsStatic)
		{	
			output += "})();";
		}
		
		return output;
	};
	
	this.ToStaticJs = function()
	{
		if (_staticConstructor == null)
		{
			return "";
		}
		
		var constructorName = "__" + _name + "StaticConstructor";
		return constructorName + "();delete " + constructorName + ";";
	}
};

/* bool */ Class.IsMatching = function(/* Concept */ concept)
{
	if (!(concept instanceof Concept))
	{
		throw "ArgumentTypeException";
	}

	return Class.RegExp.test(concept.Head);
};

(function()
{
	var _pattern = "^(static[ ])?class " + Patterns.Identifier + "$";
	
	var _regExp = new RegExp(_pattern);
	Class.__defineGetter__("RegExp", function() { return _regExp; });
	Class.__defineSetter__("RegExp", function() { throw "MethodAccessibilityException"; });
	
	var _staticPosition = 1;
	Class.__defineGetter__("StaticPosition", function() { return _staticPosition; });
	Class.__defineSetter__("StaticPosition", function() { throw "MethodAccessibilityException"; });
	
	var _namePosition = 2;
	Class.__defineGetter__("NamePosition", function() { return _namePosition; });
	Class.__defineSetter__("NamePosition", function() { throw "MethodAccessibilityException"; });
})();

#endif

